package com.fxu.framework.core.sql;

import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.support.IdeaProxyLambdaMeta;
import lombok.AllArgsConstructor;

import java.io.*;
import java.lang.invoke.SerializedLambda;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Lambda 思想类（主要是根据Lambda表达式获取SFunc元素）
 *
 * @author fangxu
 * @version 1.0.0
 */
@AllArgsConstructor
public class SLambda implements Serializable {
    private static final long serialVersionUID = 8025925345765570181L;

    private String implClass;
    private String implMethodName;

    /**
     * 获取 class 的名称
     *
     * @return 类名
     */
    public String getImplClassName() {
        return implClass.replace('/', '.');
    }

    /**
     * 获取实现者的方法名称
     *
     * @return 方法名称
     */
    public String getImplMethodName() {
        return implMethodName;
    }

    /**
     * 获取实现的 class
     *
     * @return 实现类
     */
    public Class<?> getImplClass() {
        return ClassUtils.toClassConfident(getImplClassName());
    }

    /**
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param func 需要解析的 lambda 对象
     * @param <T>  类型，被调用的 Function 对象的目标类型
     * @return 返回解析后的结果
     */
    public static <T> SLambda extractBySerialize(SFunc<T, ?> func) {
        // 1. IDEA 调试模式下 lambda 表达式是一个代理
        if (func instanceof Proxy) {
            IdeaProxyLambdaMeta proxyLambdaMeta = new IdeaProxyLambdaMeta((Proxy) func);
            //TODO 需要验证下 => ideaProxyLambdaMeta.getInstantiatedClass()
            return new SLambda(proxyLambdaMeta.getInstantiatedClass().getName(), proxyLambdaMeta.getImplMethodName());
        }
        // 2. 反射读取
        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            SerializedLambda serializedLambda = (SerializedLambda) ReflectionKit.setAccessible(method).invoke(func);
            return new SLambda(serializedLambda.getImplClass(), serializedLambda.getImplMethodName());
        } catch (Throwable e) {
            // 3. 反射失败使用序列化的方式读取
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 ObjectOutputStream oos = new ObjectOutputStream(baos)) {
                oos.writeObject(func);
                oos.flush();
                try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())) {
                    @Override
                    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                        Class<?> clazz = super.resolveClass(desc);
                        return clazz == SerializedLambda.class ? SLambda.class : clazz;
                    }

                }) {
                    return (SLambda) ois.readObject();
                }
            } catch (IOException | ClassNotFoundException e1) {
                throw new RuntimeException("SLambda extract error!" + e1);
            }
        }
    }

    public static <T> SLambda extractByCache(SFunc<T, ?> func) {
        String name = func.getClass().getName();
        return Optional.ofNullable(FUNC_CACHE.get(name)).map(Reference::get).orElseGet(() -> {
            SLambda lambda = extractBySerialize(func);
            FUNC_CACHE.put(name, new WeakReference(lambda));
            return lambda;
        });
    }

    private static Map<String, WeakReference<SLambda>> FUNC_CACHE = new ConcurrentHashMap();

    /*public static <T> SLambda resolveByCache(SFunc<T, ?> func) {
        String name = func.getClass().getName();
        return Optional.ofNullable(FUNC_CACHE.get(name)).map(Reference::get).orElseGet(() -> {
            SLambda lambda = resolveBySerialize(func);
            FUNC_CACHE.put(name, new WeakReference(lambda));
            return lambda;
        });
    }

    private static SLambda resolveBySerialize(SFunc<?, ?> lambda) {
        if (!lambda.getClass().isSynthetic()) {
            throw new RuntimeException("该方法仅能传入 lambda 表达式产生的合成类 => " + new Object[0]);
        }
        try {
            ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
                protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
                    Class clazz;
                    try {
                        clazz = ClassUtils.toClassConfident(objectStreamClass.getName());
                    } catch (Exception var4) {
                        clazz = super.resolveClass(objectStreamClass);
                    }
                    return clazz == SerializedLambda.class ? SLambda.class : clazz;
                }
            };
            SLambda var3;
            try {
                var3 = (SLambda)objIn.readObject();
            } catch (Throwable var13) {
                throw var13;
            } finally {
                if (objIn != null) {
                    objIn.close();
                }
            }
            return var3;
        } catch (IOException | ClassNotFoundException var15) {
            throw new RuntimeException("This is impossible to happen =>" + new Object[0], var15);
        }
    }*/
}